package com.ideaaedi.springcloud.jd.commonspring.refresher;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ideaaedi.commonds.io.IOUtil;
import com.ideaaedi.commonds.path.PathUtil;
import com.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonds.constant.JdSymbolConstant;
import com.ideaaedi.springcloud.jd.commonds.enums.POState;
import com.ideaaedi.springcloud.jd.commonds.enums.POSubstate;
import com.ideaaedi.springcloud.jd.commonds.support.EnumDescriptor;
import com.ideaaedi.springcloud.jd.commonds.support.Locator;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * 刷新state-substate说明文档<br/>
 * 注：在本地环境调试时会进行刷新. 且当次刷新的文档，要下次启动时才能看到.（因为此文档即时性要求不高，先这样简单处理.）
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.5.B
 */
@Slf4j
@Component
@Profile(BaseConstant.ENV_DEV)
public class StateMarkdownRefresher implements ApplicationRunner {

    @Value("${api-doc.state.markdown.title:state编码说明}")
    private String stateMdTitle;

    @Value("${api-doc.substate.markdown.title:substate编码说明}")
    private String substateMdTitle;

    @Value("${api-doc.state-substate.markdown.table-headers:序号,相关表,相关列,code,描述}")
    private String stateSubststeMdTableHeaders;

    @Override
    public void run(ApplicationArguments args) throws ClassNotFoundException {
        String projectRootDir = PathUtil.getProjectRootDir(StateMarkdownRefresher.class);
        boolean isJar = projectRootDir.endsWith(".jar");
        if (isJar) {
            log.info("curr run env is jar. skip refresh state substate markdown.");
            return;
        }
        String stateMdFilepath = projectRootDir.replace("/target/classes/",
                "/src/main/resources/api-doc-common/state.md");
        boolean filepathIsCorrect = new File(stateMdFilepath).exists();
        if (!filepathIsCorrect) {
            log.info("filepath is not correct. skip refresh state substate markdown. determine stateMdFilepath -> {}", stateMdFilepath);
            return;
        }
        String substateMdFilepath = projectRootDir.replace("/target/classes/",
                "/src/main/resources/api-doc-common/substate.md");
        filepathIsCorrect = new File(substateMdFilepath).exists();
        if (!filepathIsCorrect) {
            log.info("filepath is not correct. skip refresh state substate markdown. determine substateMdFilepath -> {}", substateMdFilepath);
            return;
        }
        
        // 准备state数据
        List<List<String>> stateRowList = prepareData(POState.class);
        // 刷新markdown文件
        generateMdTable(stateMdFilepath, stateMdTitle, stateRowList);
        log.info("refresh state markdown. see {}", stateMdFilepath);
    
        // 准备substate数据
        List<List<String>> substateRowList = prepareData(POSubstate.class);
        // 刷新markdown文件
        generateMdTable(substateMdFilepath, substateMdTitle, substateRowList);
        log.info("refresh substate markdown. see {}", substateMdFilepath);
    }

    /**
     * 准备数据
     */
    @NotNull
    private List<List<String>> prepareData(Class<?> clazz) throws ClassNotFoundException {
        List<List<String>> rowList = new ArrayList<>(128);
        rowList.add(Arrays.asList(stateSubststeMdTableHeaders.split(JdSymbolConstant.COMMA)));
        Class<?>[] declaredClasses = clazz.getDeclaredClasses();
        for (Class<?> declaredClass : declaredClasses) {
            if (!declaredClass.isEnum()) {
                continue;
            }
            if (!Locator.class.isAssignableFrom(declaredClass)) {
                continue;
            }
            if (!EnumDescriptor.class.isAssignableFrom(declaredClass)) {
                continue;
            }
            if (!Supplier.class.isAssignableFrom(declaredClass)) {
                continue;
            }
            for (Enum<?> enumConstant : ((Class<Enum<?>>) declaredClass).getEnumConstants()) {
                Integer code = ((Supplier<Integer>) enumConstant).get();
                String desc = ((EnumDescriptor) enumConstant).obtainDescription();
                Field stateField = ((Locator<Field>) enumConstant).locate();
                TableField tableFieldAnno = stateField.getAnnotation(TableField.class);
                TableName tableNameAnno = stateField.getDeclaringClass().getAnnotation(TableName.class);
                ApiModelProperty apiModelPropertyAnno = stateField.getAnnotation(ApiModelProperty.class);
                Objects.requireNonNull(tableFieldAnno, "tableFieldAnno cannot be null.");
                Objects.requireNonNull(tableNameAnno, "tableNameAnno cannot be null");
                ApiModel apiModelAnno = stateField.getDeclaringClass().getAnnotation(ApiModel.class);
                String column = tableFieldAnno.value();
                String table = tableNameAnno.value();
                String fieldComment = apiModelPropertyAnno == null ? "" : apiModelPropertyAnno.value();
                String tableComment = apiModelAnno == null ? "" : apiModelAnno.description();
                if (fieldComment.contains("(")) {
                    fieldComment = fieldComment.substring(0, fieldComment.indexOf("("));
                }
                if (fieldComment.contains("（")) {
                    fieldComment = fieldComment.substring(0, fieldComment.indexOf("（"));
                }
                if (tableComment.contains("(")) {
                    tableComment = tableComment.substring(0, tableComment.indexOf("("));
                }
                if (tableComment.contains("（")) {
                    tableComment = tableComment.substring(0, tableComment.indexOf("（"));
                }
                List<String> rowItemslist = new ArrayList<>(4);
                rowItemslist.add(String.valueOf(rowList.size()));
                rowItemslist.add(table + (StringUtils.isBlank(tableComment) ? "" : ("（" + tableComment + "）")));
                rowItemslist.add(column + (StringUtils.isBlank(fieldComment) ? "" : ("（" + fieldComment + "）")));
                rowItemslist.add(String.valueOf(code));
                rowItemslist.add(desc);
                rowList.add(rowItemslist);
            }
        }
        return rowList;
    }

    /**
     * 生成md表格文件
     * <p>
     * <p>
     * 使用示例
     * <pre>
     *    public static void main(String[] args) {
     *         List<List<String>> dataList = new ArrayList<>();
     *         dataList.add(Lists.newArrayList("序号","code","msg","说明"));
     *         dataList.add(Lists.newArrayList("1","000000","success","成功"));
     *         dataList.add(Lists.newArrayList("2","100000","fail","失败"));
     *         generateMdTable("/tmp/code-msg-desc.md", "响应编码字典", dataList);
     *     }
     * </pre>
     *
     * @param filepath md文件路径。形如：/tmp/code-msg-desc.md
     * @param title 文件内容的标题（一级标题）
     * @param dataList 文件内容（第一个list为标题, dataList中的每个list的size可以不一样， 以标题list的size为准）
     */
    private static void generateMdTable(String filepath, String title, List<List<String>> dataList) {
        final String colSeparator = "|";
        final String headerSeparator = "---";
        final String blank = " ";
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        StringBuilder mdBuilder = new StringBuilder(512);

        /// step1. 构建md标题
        mdBuilder.append("# ").append(title);
        mdBuilder.append(System.lineSeparator());

        /// step2. 构建md表格
        List<String> headerItemList = dataList.get(0);
        int headerItemSize = headerItemList.size();
        // 组装头，如： |  姓名  | 年龄 |
        for (String headerItem : headerItemList) {
            mdBuilder.append(colSeparator).append(blank).append(headerItem).append(blank);
        }
        mdBuilder.append(colSeparator).append(System.lineSeparator());

        // 组装分割线，如：| ---- | ---- |
        for (int i = 0; i < headerItemSize; i++) {
            mdBuilder.append(colSeparator).append(blank).append(headerSeparator).append(blank);
        }
        mdBuilder.append(colSeparator).append(System.lineSeparator());

        // 组装内容，如：| 张三 | 30 |
        for (int i = 1; i < dataList.size(); i++) {
            List<String> colItemList = dataList.get(i);
            Objects.requireNonNull(colItemList, "colItemList should not be null.");
            int colItemSize = colItemList.size();
            for (int j = 1; j <= headerItemSize; j++) {
                mdBuilder.append(colSeparator).append(blank).append(j <= colItemSize ? colItemList.get(j-1) : blank).append(blank);
            }
            mdBuilder.append(colSeparator).append(System.lineSeparator());
        }

        IOUtil.writeContentToFile(mdBuilder.toString(), new File(filepath));
    }
}
